/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2006, University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA
 */

package edu.umd.cs.findbugs.gui2.sort;

import static edu.umd.cs.findbugs.L10N.getLocalString;

import java.sql.Timestamp;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

import edu.umd.cs.findbugs.AppVersion;
import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugPattern;
import edu.umd.cs.findbugs.BugRanker;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.I18N;
import edu.umd.cs.findbugs.ProjectPackagePrefixes;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.gui2.BugLeafNode;
import edu.umd.cs.findbugs.gui2.BugSet;
import edu.umd.cs.findbugs.gui2.GUISaveState;
import edu.umd.cs.findbugs.gui2.MainFrame;
import edu.umd.cs.findbugs.gui2.BugAspects.SortableValue;
import edu.umd.cs.findbugs.util.ClassName;

/**
 * A useful enum for dealing with all the types of filterable and sortable data in BugInstances This
 * is the preferred way for getting the information out of a BugInstance and formatting it for
 * display It also has the comparators for the different types of data
 * 
 * @author Reuven
 */

public enum Sortables implements Sortable {

	/**
	 * The enum members originally implemented the interface, but to allow different kinds of
	 * sortables, and still allow references by an enum, each enum member has been extracted into a
	 * separate inner class. The enum itself can still be referenced, but will not be the sortable
	 * directly.
	 */

	FIRSTVERSION(new FirstVersionSortable(getLocalString("sort.first_version", "First Version"))),
	LASTVERSION(new LastVersionSortable(getLocalString("sort.last_version", "Last Version"))),
	PRIORITY(new PrioritySortable(getLocalString("sort.priority", "Priority"))),
	CLASS(new ClassSortable(getLocalString("sort.class", "Class"))),
	PACKAGE(new PackageSortable(getLocalString("sort.package", "Package"))),
	PACKAGE_PREFIX(new PackagePrefixSortable(getLocalString("sort.package_prefix", "Package prefix"))),
	CATEGORY(new CategorySortable(getLocalString("sort.category", "Category"))),
	DESIGNATION(new DesignationSortable(getLocalString("sort.designation", "Designation"))),
	BUGCODE(new BugcodeSortable(getLocalString("sort.bug_kind", "Bug Kind"))),
	TYPE(new TypeSortable(getLocalString("sort.bug_pattern", "Bug Pattern"))),
	BUG_RANK(new BugRankSortable(getLocalString("sort.bug_bugrank", "Bug Rank"))),
	PROJECT(new ProjectSortable(getLocalString("sort.bug_project", "Project"))),
	DIVIDER(new DividerSortable());
	

	private Sortable theSortable;

	Sortables(Sortable theSortable) {
		this.theSortable = theSortable;
	}

	@CheckForNull
	public static Sortable getSortableByPrettyName(String name) {
		for (Sortables s : values()) {
			if (s.theSortable.prettyName().equals(name))
				return s;
		}
		return null;
	}

	@CheckForNull
	public static Sortables getMemberBySortable(Sortable sortable) {
		for (Sortables s : values()) {
			if (s == sortable || s.theSortable.equals(sortable)) {
				return s;
			}
		}
		return null;
	}

	public int compare(SortableValue one, SortableValue two) {
		return theSortable.compare(one, two);
	}

	public String formatValue(String value) {
		return theSortable.formatValue(value);
	}

	public String[] getAll() {
		return theSortable.getAll();
	}

	public String[] getAll(BugSet set) {
		return theSortable.getAll(set);
	}

	public String[] getAllSorted() {
		return theSortable.getAllSorted();
	}

	public String[] getAllSorted(BugSet set) {
		return theSortable.getAllSorted(set);
	}

	public Comparator<BugLeafNode> getBugLeafNodeComparator() {
		return theSortable.getBugLeafNodeComparator();
	}

	public SortableStringComparator getComparator() {
		return theSortable.getComparator();
	}

	public String getFrom(BugInstance bug) {
		return theSortable.getFrom(bug);
	}

	public boolean isAvailable(MainFrame frame) {
		return theSortable.isAvailable(frame);
	}

	public String prettyName() {
		return theSortable.prettyName();
	}	
	
	@Override
	public String toString() {
		return theSortable.toString();
	}

	private static final class FirstVersionSortable extends AbstractSortable {
		public FirstVersionSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			return Long.toString(bug.getFirstVersion());
		}

		@Override
		public String formatValue(String value) {
			int seqNum = Integer.parseInt(value);
			BugCollection bugCollection = MainFrame.getInstance().bugCollection();
			if (bugCollection == null)
				return "--";
			AppVersion appVersion = bugCollection.getAppVersionFromSequenceNumber(seqNum);
			String appendItem = "";
			if (appVersion != null) {
				String timestamp = new Timestamp(appVersion.getTimestamp()).toString();
				appendItem = appVersion.getReleaseName() + " (" + timestamp.substring(0, timestamp.indexOf(' ')) + ")";
			}
			if (appendItem == "")
				appendItem = "#" + seqNum;
			return appendItem;
		}

		@Override
		public int compare(SortableValue one, SortableValue two) {
			// Numerical (zero is first)
			return Integer.valueOf(one.value).compareTo(Integer.valueOf(two.value));
		}
	}

	private static final class LastVersionSortable extends AbstractSortable {
		public LastVersionSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			return Long.toString(bug.getLastVersion());
		}

		@Override
		public String formatValue(String value) {
			// System.out.println("Formatting last version value");
			if (value.equals("-1"))
				return "";
			int seqNum = Integer.parseInt(value);
			BugCollection bugCollection = MainFrame.getInstance().bugCollection();
			if (bugCollection == null)
				return "--";
			AppVersion appVersion = bugCollection.getAppVersionFromSequenceNumber(seqNum);
			String appendItem = "";
			if (appVersion != null) {
				String timestamp = new Timestamp(appVersion.getTimestamp()).toString();
				appendItem = appVersion.getReleaseName() + " (" + timestamp.substring(0, timestamp.indexOf(' ')) + ")";
			}
			if (appendItem == "")
				appendItem = "#" + seqNum;
			return appendItem;
		}

		@Override
		public int compare(SortableValue one, SortableValue two) {
			if (one.value.equals(two.value))
				return 0;

			// Numerical (except that -1 is last)
			int first = Integer.valueOf(one.value);
			int second = Integer.valueOf(two.value);
			if (first == second)
				return 0;
			if (first < 0)
				return 1;
			if (second < 0)
				return -1;
			if (first < second)
				return -1;
			return 1;
		}
	}

	private static final class PrioritySortable extends AbstractSortable {
		public PrioritySortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			return String.valueOf(bug.getPriority());
		}

		@Override
		public String formatValue(String value) {
			if (value.equals(String.valueOf(Detector.HIGH_PRIORITY)))
				return edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_high", "High");
			if (value.equals(String.valueOf(Detector.NORMAL_PRIORITY)))
				return edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_normal", "Normal");
			if (value.equals(String.valueOf(Detector.LOW_PRIORITY)))
				return edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_low", "Low");
			if (value.equals(String.valueOf(Detector.EXP_PRIORITY)))
				return edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_experimental", "Experimental");

			// probably shouldn't ever happen, but what the hell, let's be complete
			return edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_ignore", "Ignore"); // This
		}

		@Override
		public int compare(SortableValue one, SortableValue two) {
			// Numerical
			return Integer.valueOf(one.value).compareTo(Integer.valueOf(two.value));
		}
	}

	private static final class ClassSortable extends AbstractSortable {

		public ClassSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			return bug.getPrimarySourceLineAnnotation().getClassName();
		}

		@Override
		public int compare(SortableValue one, SortableValue two) {
			// If both have dollar signs and are of the same outer class, compare the numbers after
			// the dollar signs.
			try {
				if (one.value.contains("$")
						&& two.value.contains("$")
						&& one.value.substring(0, one.value.lastIndexOf("$")).equals(
								two.value.substring(0, two.value.lastIndexOf("$"))))
					return Integer.valueOf(one.value.substring(one.value.lastIndexOf("$"))).compareTo(
							Integer.valueOf(two.value.substring(two.value.lastIndexOf("$"))));
			} catch (NumberFormatException e) {
			} // Somebody's playing silly buggers with dollar signs, just do it lexicographically

			// Otherwise, lexicographicalify it
			return one.value.compareTo(two.value);
		}
	}

	private static final class PackageSortable extends AbstractSortable {
		public PackageSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			return bug.getPrimarySourceLineAnnotation().getPackageName();
		}

		@Override
		public String formatValue(String value) {
			if (value.equals(""))
				return "(Default)";
			return value;
		}
	}

	private static final class PackagePrefixSortable extends AbstractSortable {
		public PackagePrefixSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			int count = GUISaveState.getInstance().getPackagePrefixSegments();

			if (count < 1)
				count = 1;
			String packageName = bug.getPrimarySourceLineAnnotation().getPackageName();
			return ClassName.extractPackagePrefix(packageName, count);
		}

		@Override
		public String formatValue(String value) {
			return value + "...";
		}
	}

	private static final class CategorySortable extends AbstractSortable {
		public CategorySortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {

			BugPattern bugPattern = bug.getBugPattern();
			if (bugPattern == null) {
				return "?";
			}
			return bugPattern.getCategory();
		}

		@Override
		public String formatValue(String value) {
			return I18N.instance().getBugCategoryDescription(value);
		}

		@Override
		public int compare(SortableValue one, SortableValue two) {
			String catOne = one.value;
			String catTwo = two.value;
			int compare = catOne.compareTo(catTwo);
			if (compare == 0)
				return 0;
			if (catOne.equals("CORRECTNESS"))
				return -1;
			if (catTwo.equals("CORRECTNESS"))
				return 1;
			return compare;

		}

	}

	private static final class DesignationSortable extends AbstractSortable {
		public DesignationSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			return bug.getUserDesignationKey();
		}

		/**
		 * value is the key of the designations.
		 * 
		 * @param value
		 * @return
		 */
		@Override
		public String formatValue(String value) {
			return I18N.instance().getUserDesignation(value);
		}

		@Override
		public String[] getAllSorted() {// FIXME I think we always want user to see all possible
			// designations, not just the ones he has set in his
			// project, Agreement? -Dan
			List<String> sortedDesignations = I18N.instance().getUserDesignationKeys(true);
			return sortedDesignations.toArray(new String[sortedDesignations.size()]);
		}
	}

	private static final class BugcodeSortable extends AbstractSortable {
		public BugcodeSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			BugPattern bugPattern = bug.getBugPattern();
			if (bugPattern == null)
				return null;
			return bugPattern.getAbbrev();
		}

		@Override
		public String formatValue(String value) {
			return I18N.instance().getBugTypeDescription(value);
		}

		@Override
		public int compare(SortableValue one, SortableValue two) {
			return formatValue(one.value).compareTo(formatValue(two.value));
		}
	}

	private static final class TypeSortable extends AbstractSortable {
		public TypeSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			if ((bug.getBugPattern()) == null)
				return "?";
			else
				return bug.getBugPattern().getType();
		}

		@Override
		public String formatValue(String value) {
			return I18N.instance().getShortMessageWithoutCode(value);
		}
	}

	private static final class BugRankSortable extends AbstractSortable {
		String[] values;

		public BugRankSortable(String prettyName) {
			super(prettyName);

			values = new String[40];
			for (int i = 0; i < values.length; i++)
				values[i] = String.format("%2d", i);
		}

		@Override
		public String getFrom(BugInstance bug) {
			if ((bug.getBugPattern()) == null)
				return "??";

			int rank = BugRanker.findRank(bug);
			return values[rank];
		}

		@Override
		public String formatValue(String value) {
			return value;
		}
	}

	private static final class ProjectSortable extends AbstractSortable {

		public ProjectSortable(String prettyName) {
			super(prettyName);
		}

		@Override
		public String getFrom(BugInstance bug) {
			ProjectPackagePrefixes p = MainFrame.getInstance().projectPackagePrefixes();
			Collection<String> projects = p.getProjects(bug.getPrimaryClass().getClassName());
			if (projects.size() == 0)
				return "unclassified";
			String result = projects.toString();

			return result.substring(1, result.length() - 1);
		}

		@Override
		public boolean isAvailable(MainFrame mf) {
			return mf.projectPackagePrefixes().size() > 0;
		}

	}

	private static final class DividerSortable extends AbstractSortable {
		public DividerSortable() {
			super(" ");
		}

		@Override
		public String getFrom(BugInstance bug) {
			throw new UnsupportedOperationException();
		}

		@Override
		public String[] getAll() {
			throw new UnsupportedOperationException();
		}

		@Override
		public String formatValue(String value) {
			throw new UnsupportedOperationException();
		}

		@Override
		public int compare(SortableValue one, SortableValue two) {
			throw new UnsupportedOperationException();
		}
	}

}
